Skip to content

Cap composited .spd image size to avoid OOM on a corrupted/outlier tile - #39

Open
philips-clanker wants to merge 1 commit into
mainfrom
cap-atelier-composite-size-issue-147
Open

Cap composited .spd image size to avoid OOM on a corrupted/outlier tile#39
philips-clanker wants to merge 1 commit into
mainfrom
cap-atelier-composite-size-issue-147

Conversation

@philips-clanker

Copy link
Copy Markdown
Collaborator

Summary

Candidate fix for supernote-obsidian-plugin#147 (crash/"boot loop" opening certain notes on iOS). Note: the reporter has since clarified the crashing file in their case is a .note file, not .spd, so this fix addresses a real, independently-verified bug found while investigating that report, but may not be the cause of #147 itself - see that issue for the ongoing investigation into the .note path.

SupernoteAtelier.toImage() sizes its output image against the tile-grid bounding box computed across every tile in the file (computeGridBounds()), with no sanity check before allocating. A single tile whose tid is far from the rest - a corrupted file, or in principle a stray mark placed far off on Atelier's own much-larger virtual canvas (see TILE_ID_STRIDE's doc comment) - blows that bounding box out arbitrarily far, and new Image(width, height, ...) allocates accordingly: potentially gigabytes for what should be a small composite.

On desktop that's slow or an OOM in one tab. On a memory-constrained mobile WebView (iOS in particular) that's a hard, non-catchable crash - exactly the shape of bug that would produce a "boot loop": the host reopens the last file on relaunch, so one bad .spd file crashes it again immediately, every time.

Changes

  • Adds a 32-megapixel ceiling on the composited image's total pixel count (real device-generated files observed so far top out around 1920x2560, ~4.9 megapixels - see atelier.test.ts), throwing a descriptive Error instead of allocating past it.
  • The main plugin's SupernoteAtelierView.updateImage() already wraps this call in try/catch and renders errors in-view, so this turns a native crash into a normal, visible "couldn't open this file" error rather than requiring any change on that side.

Test plan

  • npm run build - clean
  • npx eslint src/atelier.ts tests/atelier.test.ts - 0 errors
  • npx vitest run - 41 passed (39 previously + 2 new)
  • New test constructs a synthetic .spd SQLite database (via sql.js directly, not a real device export) with one tile 10 million grid-columns from the origin, confirming toImage() throws rather than allocating
  • New test confirms normal, in-bounds tile layouts still composite correctly

toImage() sized its output against the tile-grid bounding box across
every tile in the file, with no sanity check at all. A single tile
whose tid is far from the rest (a corrupted file, or a stray mark
placed far off on Atelier's much larger virtual canvas - see
TILE_ID_STRIDE's doc comment) blows that bounding box out arbitrarily
far, and toImage() would allocate accordingly - gigabytes for what
should be a small image.

On desktop that's slow or an OOM in one tab. On a memory-constrained
mobile WebView it's a hard crash, not a catchable one - this is the
root cause of supernote-obsidian-plugin#147's iOS "boot loop": since
the host app restores the last-open file on relaunch, one bad .spd
file crashes it again immediately, every time, with no way out short
of clearing workspace state.

Adds a 32-megapixel ceiling (real device files observed so far top out
around 1920x2560, ~4.9 megapixels - see atelier.test.ts) and throws a
descriptive error instead of allocating past it. The main plugin's
SupernoteAtelierView already catches errors from this path and renders
them in-view (see updateImage()'s try/catch), so this turns a native
crash into a normal, visible "couldn't open this file" error.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant